Validation
Validation framework
Validation overview
DataObjects.Net includes consistency validation framework allowing to validate single property values, entire entities and entity graphs.
Each session contains its own ValidationContext – class
responsible for validation of all entities changed in this session.
Since the context is session-level, validation can be turned on or off
independantly for each session by including or excluding SessionOption.ValidateEntitites
option. By default the option is included in all session profiles.
Validation context can validate entities:
On attempt to change field value. In this case only changing field is validated. If field has any Immediate validators (more on that in Property contraints chapter) they will be triggered one by one. If certain validator returns an error anArgumentExceptionwith error message from validator will be thrown.On transaction commit. When an entity is changed it register itself in validation context for future validation. All registered entities are validated when transaction is about to commit. When it happens all validators are triggered (except for those which are immediate) and if validation is failed validation infrastructure will throw anValidationFailedExceptionwith list of validation errors grouped by entities.
Following example shows where to expect exceptions in both cases
try {
  using (var transactionScope = session.OpenTransaction()) {
    var user = session.Query.All<User>().First(u => u.Id == currentUserId);
    try {
      // both names have immediate validator for value not being null or empty
      user.FirstName = updatedFirstName;
      user.LastName = updatedLastName;
    }
    catch(ArgumentException argException){
      Console.WriteLine("Following validation error was found on attempt to update entity:");
      Console.WriteLine(argException.Message);
      throw;
    }
    // email validator is not immediate
    user.Email = updateEmail;
    user.LastUpdateOn = DateTime.UtcNow;
    transactionScope.Complete();
  }
}
catch(ValidationFailedException exception) {
  Console.WriteLine("Following validation errors were found:");
  foreach (var errorInfo in exception.ValidationErrors) {
    var entity = errorInfo.Target;
    Console.WriteLine("- {entity.ToString()}:");
    foreach (var validationResult in errorInfo.Errors) {
      var errorMessage = validationResult.ErrorMessage;
      var fieldName = validationResult.Field.Name;
      Console.WriteLine($"  Field {fieldName} caused '{errorMessage}'");
    }
    Console.WriteLine();
  }
}
You can aslo perfrom validation manually. Entity.Validate() will validate
separate entity, if validation failed it will throw ValidationFailedException.
Session.Validate() will validate all entities registered in the validation
context at once. Sometimes you also might want to have errors in form of collection
instead of exception, if so the Session.ValidateAndGetErrors() method will
be useful, it will give you error information like it presented in
ValidationFailedException.ValidationErrors.
Object level validation rules
To define validation rules you should implement object-level validation
logic in OnValidate() method that will be called on entity
validation. This method should check entity state and throw an exception
if it is invalid.
[HierarchyRoot]
public class Person : Entity
{
  // ...
  [Field]
  public bool IsSubscribedOnNews { get; set;}
  [Field]
  public string Email { get; set;}
  protected override void OnValidate()
  {
    base.OnValidate();
    if (IsSubscribedOnNews && string.IsNullOrEmpty(Email))
      throw new Exception("Can't subscribe on news (email is not specified).");
  }
}
Property constraints
Overview
Another way to define validation rules is to mark entity properties by
special attributes – property constraints. Property constraints are
special property-level validation aspects that can be applied to any
property by marking it with appropriate attribute. Each constraint
implements some simple validation rule for property value. For example
NotNullConstraint ensures that property value is not null,
RegexConstraint ensures that string value matches specified regular
expression pattern.
[LengthConstraint(Min = 2, Max = 128)]
[NotNullOrEmptyConstraint]
public string FirstName { get; set;}
[PastConstraint]
public DateTime BirthDay { get; set; }
[EmailConstraint]
public string Email { get; set;}
Each property constraint attribute has additional properties:
IsImmediate. Set totrue, it tells to validate corresponding field imediately on attempt to set value.falsewill postpone until transaction commitValidateOnlyIfModified. By default, validation happens for all validators of an entity - even if some field has not been modified its value will be validated. This option forces not to validate field by the validator if the field value hasn’t been changed.SkipOnTransactionCommitallows to skip validation of by the validator on transaction commit.
[PastConstraint(
  IsImmediate = true,
  ValidateOnlyIfModified = true)]
public DateTime BirthDay { get; set; }
[RangeConstraint(
  Min = 0.8,
  Max = 2.13,
  IsImmediate = true,
  SkipOnTransactionCommit = true)]
public double Height { get; set;}
Pre-defined validation aspects
DataObjects.Net validation framework includes following predefined constraints:
EmailConstraint– Ensures that email address is in correct formatFutureConstraint– Ensures that date value is in the futureLengthConstraint(Min, Max)– Ensures field length (or item count) fits in specified rangeNotEmptyConstraint– Ensures that property value is not empty stringNotNullConstraint– Ensures property value is not nullNotNullOrEmptyConstraint– Ensures that property value is not null or empty stringPastConstraint– Ensures that date value is in the pastRangeConstraint(Min, Max)– Ensures field value fits in the specified rangeRegexConstraint(Pattern)– Ensures property value matches specified regular expression
Implementing custom constraints
It is possible to implement your own property constraints inheriting it
from abstract class PropertyValidator.
[Serializable]
public class PhoneNumberConstraint : PropertyValidator
{
  private const string PhoneNumberPattern = "^[2-9]\\d{2}-\\d{3}-\\d{4}$";
  private static readonly Regex Validator = new Regex(PhoneNumberPattern);
  public override void Configure(Domain domain, TypeInfo type, FieldInfo field)
  {
    base.Configure(domain, type, field);
    if (field.ValueType!=typeof (string))
      ThrowConfigurationError(string.Format(Strings.FieldShouldBeOfTypeX, typeof (string).FullName));
  }
  public override ValidationResult Validate(Entity target, object fieldValue)
  {
    var value = (string) fieldValue;
    var isValid = string.IsNullOrEmpty(value) || Validator.IsMatch(value);
    return isValid ? Success() : Error("Phone number is incorrect", fieldValue);
  }
  public override IPropertyValidator CreateNew()
  {
    return new PhoneNumberConstraint {
      IsImmediate = IsImmediate,
    };
  }
}
In Configure method we can check whether constraint is applied properly,
for instance here we check that it is applied to field of string type.
Validate defines validation algorithm itself, as a result you can
return Success or Error with error message or exception instance
and actual value that caused the error.
CreateNew is used by infrastructure to clone instance of attribute,
you just need to return new instance of the validator with exact same settings.
[PhoneNumberConstraint]
public string Phone { get; set;}